/*
 * Copyright (c) 2020.Huawei Technologies Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <iostream>
#include <memory>
#include "acl/acl.h"
#include "acl/acl_rt.h"
#include "Log/Log.h"
#include "CommonDataType/CommonDataType.h"
#include "DvppCommon.h"


DvppCommon::DvppCommon(aclrtStream dvppStream)
{
    dvppStream_ = dvppStream;
}

DvppCommon::DvppCommon(const VdecConfig &vdecConfig)
{
    vdecConfig_ = vdecConfig;
}

APP_ERROR DvppCommon::CheckVdecConfig()
{
    if (vdecConfig_.inputWidth > (int)MAX_VDEC_INPUT_WIDTH || vdecConfig_.inputWidth < (int)MIN_VDEC_INPUT_WIDTH) {
        LogError << "Invalid input width, current input width is " << vdecConfig_.inputWidth \
        << ", not in range [128, 4096]";
        return APP_ERR_ACL_INVALID_PARAM;
    }
    if (vdecConfig_.inputHeight > (int)MAX_VDEC_INPUT_HEIGHT || vdecConfig_.inputHeight < (int)MIN_VDEC_INPUT_HEIGHT) {
        LogError << "Invalid input height, current input height is " << vdecConfig_.inputHeight \
        << ", not in range [128, 4096]";
        return APP_ERR_ACL_INVALID_PARAM;
    }
    return APP_ERR_OK;
}

/*
 * @description: Release the memory that is allocated in the interfaces which are started with "Combine"
 */
void DvppCommon::ReleaseDvppBuffer() const
{
    if (cropImage_ != nullptr) {
        RELEASE_DVPP_DATA(cropImage_->data);
    }
    if (resizedImage_ != nullptr) {
        RELEASE_DVPP_DATA(resizedImage_->data);
    }
    if (decodedImage_ != nullptr) {
        RELEASE_DVPP_DATA(decodedImage_->data);
    }
    if (inputImage_ != nullptr) {
        RELEASE_DVPP_DATA(inputImage_->data);
    }
    if (encodedImage_ != nullptr) {
        RELEASE_DVPP_DATA(encodedImage_->data);
    }
}

/*
 * @description: Get crop area
 * @param: input specifies the input image information
 * @param: output specifies the output image information
 * @param: processType specifies whether to perform proportional scaling
 * @param: cropRoi is used to save the info of the crop roi area
 * @return: APP_ERR_OK if success, other values if failure
 */
void DvppCommon::GetCropRoi(const DvppDataInfo &input, const DvppDataInfo &output, VpcProcessType processType,
                            CropRoiConfig &cropRoi) const
{
    // When processType is not VPC_PT_FILL, crop area is the whole input image
    if (processType != VPC_PT_FILL) {
        cropRoi.right = CONVERT_TO_ODD(input.width - ODD_NUM_1);
        cropRoi.down = CONVERT_TO_ODD(input.height - ODD_NUM_1);
        return;
    }

    bool widthRatioSmaller = true;
    // The scaling ratio is based on the smaller ratio to ensure the smallest edge to fill the targe edge
    float resizeRatio = static_cast<float>(input.width) / output.width;
    if (resizeRatio > (static_cast<float>(input.height) / output.height)) {
        resizeRatio = static_cast<float>(input.height) / output.height;
        widthRatioSmaller = false;
    }

    const int halfValue = 2;
    // The left and up must be even, right and down must be odd which is required by acl
    if (widthRatioSmaller) {
        cropRoi.left = 0;
        cropRoi.right = CONVERT_TO_ODD(input.width - ODD_NUM_1);
        cropRoi.up = CONVERT_TO_EVEN(static_cast<uint32_t>((input.height - output.height * resizeRatio) / halfValue));
        cropRoi.down = CONVERT_TO_ODD(input.height - cropRoi.up - ODD_NUM_1);
        return;
    }

    cropRoi.up = 0;
    cropRoi.down = CONVERT_TO_ODD(input.height - ODD_NUM_1);
    cropRoi.left = CONVERT_TO_EVEN(static_cast<uint32_t>((input.width - output.width * resizeRatio) / halfValue));
    cropRoi.right = CONVERT_TO_ODD(input.width - cropRoi.left - ODD_NUM_1);
    return;
}

/*
 * @description: Get paste area
 * @param: input specifies the input image information
 * @param: output specifies the output image information
 * @param: processType specifies whether to perform proportional scaling
 * @param: pasteRio is used to save the info of the paste area
 * @return: APP_ERR_OK if success, other values if failure
 */
void DvppCommon::GetPasteRoi(const DvppDataInfo &input, const DvppDataInfo &output, VpcProcessType processType,
                             CropRoiConfig &pasteRoi) const
{
    if (processType == VPC_PT_FILL) {
        pasteRoi.right = CONVERT_TO_ODD(output.width - ODD_NUM_1);
        pasteRoi.down = CONVERT_TO_ODD(output.height - ODD_NUM_1);
        return;
    }

    bool widthRatioLarger = true;
    // The scaling ratio is based on the larger ratio to ensure the largest edge to fill the targe edge
    float resizeRatio = static_cast<float>(input.width) / output.width;
    if (resizeRatio < (static_cast<float>(input.height) / output.height)) {
        resizeRatio = static_cast<float>(input.height) / output.height;
        widthRatioLarger = false;
    }

    // Left and up is 0 when the roi paste on the upper left corner
    if (processType == VPC_PT_PADDING) {
        pasteRoi.right = (input.width / resizeRatio) - ODD_NUM_1;
        pasteRoi.down = (input.height / resizeRatio) - ODD_NUM_1;
        pasteRoi.right = CONVERT_TO_ODD(pasteRoi.right);
        pasteRoi.down = CONVERT_TO_ODD(pasteRoi.down);
        return;
    }

    const int halfValue = 2;
    // Left and up is 0 when the roi paste on the middler location
    if (widthRatioLarger) {
        pasteRoi.left = 0;
        pasteRoi.right = output.width - ODD_NUM_1;
        pasteRoi.up = (output.height - (input.height / resizeRatio)) / halfValue;
        pasteRoi.down = output.height - pasteRoi.up - ODD_NUM_1;
    } else {
        pasteRoi.up = 0;
        pasteRoi.down = output.height - ODD_NUM_1;
        pasteRoi.left = (output.width - (input.width / resizeRatio)) / halfValue;
        pasteRoi.right = output.width - pasteRoi.left - ODD_NUM_1;
    }

    // The left must be even and align to 16, up must be even, right and down must be odd which is required by acl
    pasteRoi.left = DVPP_ALIGN_UP(CONVERT_TO_EVEN(pasteRoi.left), VPC_WIDTH_ALIGN);
    pasteRoi.right = CONVERT_TO_ODD(pasteRoi.right);
    pasteRoi.up = CONVERT_TO_EVEN(pasteRoi.up);
    pasteRoi.down = CONVERT_TO_ODD(pasteRoi.down);
    return;
}

/*
 * @description: Get the aligned width and height of the image after decoding
 * @param: width specifies the width before alignment
 * @param: height specifies the height before alignment
 * @param: widthStride is used to save the width after alignment
 * @param: heightStride is used to save the height after alignment
 * @return: APP_ERR_OK if success, other values if failure
 * @attention: This function is used for obtaining jpeg decode stride in Ascend 310
 */
void DvppCommon::GetJpegDecodeStrideSize(uint32_t width, uint32_t height, uint32_t &widthStride, uint32_t &heightStride)
{
    widthStride = DVPP_ALIGN_UP(width, JPEGD_STRIDE_WIDTH);
    heightStride = DVPP_ALIGN_UP(height, JPEGD_STRIDE_HEIGHT);
}

std::shared_ptr<DvppDataInfo> DvppCommon::GetInputImage() const
{
    return inputImage_;
}

std::shared_ptr<DvppDataInfo> DvppCommon::GetDecodedImage() const
{
    return decodedImage_;
}

std::shared_ptr<DvppDataInfo> DvppCommon::GetResizedImage() const
{
    return resizedImage_;
}

std::shared_ptr<DvppDataInfo> DvppCommon::GetEncodedImage() const
{
    return encodedImage_;
}

std::shared_ptr<DvppDataInfo> DvppCommon::GetCropedImage() const
{
    return cropImage_;
}

DvppCommon::~DvppCommon() {}
